# -*- coding: utf-8 -*-

import os
import cv2
import time
import queue
import serial
import string
import ctypes
import tiffile
import threading
import numpy as np
from datetime import datetime
import serial.tools.list_ports

from ASI.ASI_CAMERA_INFO import ASI_CAMERA_INFO
from ASI.ASI_ERROR_CODE import ASI_ERROR_CODE
from ASI.ASI_IMG_TYPE import ASI_IMG_TYPE
from ASI.ASI_CONTROL_TYPE import ASI_CONTROL_TYPE


# 加载SDK
sdk = ctypes.cdll.LoadLibrary("ASICamera2.dll")
# 获取连接的个数
sdk.ASIGetNumOfConnectedCameras.restype = ctypes.c_int
sdk.ASIGetNumOfConnectedCameras.argtypes = []
# 获取相机属性
sdk.ASIGetCameraProperty.restype = ASI_ERROR_CODE
sdk.ASIGetCameraProperty.argtypes = [ctypes.POINTER(ASI_CAMERA_INFO), ctypes.c_int]
# 打开相机
sdk.ASIOpenCamera.restype = ctypes.c_int
sdk.ASIOpenCamera.argtypes = [ctypes.c_int]
# 初始化相机
sdk.ASIInitCamera.restype = ctypes.c_int
sdk.ASIInitCamera.argtypes = [ctypes.c_int]
# 设置控制相机的参数
sdk.ASISetControlValue.restype = ASI_ERROR_CODE
sdk.ASISetControlValue.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_long, ctypes.c_int]
# 查看控制相机的参数
sdk.ASIGetControlValue.restype = ASI_ERROR_CODE
sdk.ASIGetControlValue.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_long),
                                   ctypes.POINTER(ctypes.c_int)]
# 定义相机的拍照格式
sdk.ASISetROIFormat.restype = ctypes.c_int
sdk.ASISetROIFormat.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int]
# 获取相机的拍照格式
sdk.ASIGetROIFormat.restype = ASI_ERROR_CODE
sdk.ASIGetROIFormat.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_long), ctypes.POINTER(ctypes.c_long),
                                ctypes.POINTER(ctypes.c_long), ctypes.POINTER(ctypes.c_long)]
# 开始录制视频
sdk.ASIStartVideoCapture.restype = ASI_ERROR_CODE
sdk.ASIStartVideoCapture.argtypes = [ctypes.c_int]
# 获取录制视频后的图像
sdk.ASIGetVideoData.restype = ASI_ERROR_CODE
sdk.ASIGetVideoData.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_long, ctypes.c_int]
# 结束录制视频
sdk.ASIStopVideoCapture.restype = ASI_ERROR_CODE
sdk.ASIStopVideoCapture.argtypes = [ctypes.c_int]
# 关闭相机
sdk.ASICloseCamera.restype = ASI_ERROR_CODE
sdk.ASICloseCamera.argtypes = [ctypes.c_int]


# 获取盘符余量
def get_free_space(path):
    free_bytes = ctypes.c_ulonglong(0)
    ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
    return free_bytes


# 获取参数数值
def get_param_value(came: int, parament: int):
    _param = ctypes.c_long()
    _status = ctypes.c_int()
    sdk.ASIGetControlValue(came,
                           parament,
                           ctypes.byref(_param),
                           ctypes.byref(_status))
    return _param.value, _status.value


# 设置参数数值
def set_param_value(came: int, parament: int, _param: ctypes.c_long, _status: ctypes.c_int):
    ret = sdk.ASISetControlValue(came, parament, _param, _status)
    return ret


# 1、获取连接个数
def acquire_connect_num():
    # 获取连接的个数
    num_cameras = sdk.ASIGetNumOfConnectedCameras()
    print(f"Step1: Number of connected cameras: {num_cameras}")


# 2、开启相机
def open_camera():
    # 打开相机
    came = ctypes.c_int()
    ret = sdk.ASIOpenCamera(came)
    if ret != 0:
        print("Failed to get camera ID")
    else:
        print(f"Step2: Open the Camera, Camera ID is: {came.value}")
    return came


# 3、设置参数自动并设置RIO
def set_camera_param(came):
    initial_camera = sdk.ASIInitCamera(came.value)
    if initial_camera == 0:
        # 3-1 开启自动曝光
        exp_param, exp_status = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_EXPOSURE)
        print(f'    expourse param value is {exp_param}, status is {exp_status}')
        gain_param, gain_status = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_GAIN)
        print(f'    gain param value is {gain_param}, status is {gain_status}')
        set_param_value(came.value, ASI_CONTROL_TYPE.ASI_EXPOSURE, ctypes.c_long(exp_param), ctypes.c_int(1))
        set_param_value(came.value, ASI_CONTROL_TYPE.ASI_GAIN, ctypes.c_long(gain_param), ctypes.c_int(1))

        # 3-2 获取分辨率和设置位深
        cam_info = ASI_CAMERA_INFO()
        sdk.ASIGetCameraProperty(ctypes.byref(cam_info), came.value)

        sdk.ASISetROIFormat(came.value,
                            ctypes.c_int(cam_info.MaxWidth), ctypes.c_int(cam_info.MaxHeight),
                            1,
                            ctypes.c_int(ASI_IMG_TYPE.ASI_IMG_RAW16))

        # 查看相机的拍照格式
        w = ctypes.c_long()
        h = ctypes.c_long()
        b = ctypes.c_long()
        t = ctypes.c_long()
        sdk.ASIGetROIFormat(came.value,
                            ctypes.byref(w), ctypes.byref(h),
                            ctypes.byref(b),
                            ctypes.byref(t))
        if w.value == cam_info.MaxWidth and h.value == cam_info.MaxHeight and t.value == ASI_IMG_TYPE.ASI_IMG_RAW16:
            print("Camera RIOFormat set Succeed!")
            return h.value, w.value, t.value
        else:
            print("Camera RIOFormat set Failed")
            return
    else:
        print("Failed to initialize the SDK")
    return


# 4、开启视频并自动调节固定帧数后关闭
def action_set(came, output_high, output_width, frames):
    # 开始录制视频
    sdk.ASIStartVideoCapture(came.value)

    buffer_size = int(output_width) * int(output_high) * 2
    buffer = (ctypes.c_ubyte * buffer_size)()
    waits = ctypes.c_int(1000)
    twice = 1
    exp, gain = None, None
    while twice < frames:
        exp, _ = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_EXPOSURE)
        gain, _ = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_GAIN)

        ret_get_data = sdk.ASIGetVideoData(came.value, buffer, buffer_size, waits)
        if ret_get_data.value == ASI_ERROR_CODE.ASI_ERROR_CAMERA_CLOSED:
            break
        elif ret_get_data.value == ASI_ERROR_CODE.ASI_ERROR_INVALID_ID:
            break
        elif ret_get_data.value == ASI_ERROR_CODE.ASI_ERROR_TIMEOUT:
            break
        else:
            np.frombuffer(buffer, dtype='uint16')
            if twice > frames:
                break
            twice += 1

    set_param_value(came.value, ASI_CONTROL_TYPE.ASI_EXPOSURE, ctypes.c_long(exp), ctypes.c_int(0))
    set_param_value(came.value, ASI_CONTROL_TYPE.ASI_GAIN, ctypes.c_long(gain), ctypes.c_int(0))

    _, exp_stat = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_EXPOSURE)
    _, gain_stat = get_param_value(came.value, ASI_CONTROL_TYPE.ASI_GAIN)
    print(f'exp_status:{exp_stat}, gain_status:{gain_stat}')
    # 结束录制视频
    sdk.ASIStopVideoCapture(came.value)


# 5、开始视频录制线程
def action_video_fun(came, output_high, output_width, img_queue):
    # 开始录制视频
    sdk.ASIStartVideoCapture(came.value)
    buffer_size = int(output_width) * int(output_high) * 2
    buffer = (ctypes.c_ubyte * buffer_size)()
    waits = ctypes.c_int(10000)
    while True:
        sdk.ASIGetVideoData(came.value, buffer, buffer_size, waits)

        video_image = np.frombuffer(buffer, dtype='uint16')
        video_image = video_image.reshape((output_high, output_width))
        img_queue.put(video_image)


# 6、计算帧率
def _calculate_fps(buf):
    frame_count = 0
    last_time = time.time()
    while True:
        buf.get()
        frame_count += 1
        elapsed_time = time.time() - last_time
        if elapsed_time > 1:
            fps = frame_count / elapsed_time
            frame_count = 0
            last_time = time.time()
            print(f"FPS: {fps:.2f}")
            if fps > 17.8:
                break
    return round(fps, 2)


# 7、开启保存线程前的准备
def _pre_paper(v):
    """

    :param v: 帧率
    :return: port_name[串口名], save_path[保存的完整的路径]
    """

    ser_name = ''
    for ser_port in serial.tools.list_ports.comports():
        res = ser_port.description
        if "340" in res:
            ser_name = ser_port.device

    com_path = ''
    drives = ['%s:' % d for d in reversed(string.ascii_uppercase) if os.path.exists('%s:' % d)]
    for drive in drives:
        space = get_free_space(drive).value
        if space >= 1*1024*1024:
            save_dir = drive
            now = datetime.now()
            name = now.strftime("%Y-%m-%d %H-%M-%S")
            com_path = f'{save_dir}/{name}.tiff'
            with open(f"{save_dir}/{name}.txt", 'w') as f:
                f.write(f'fps:{v}')
            break
    print(f'save dir:{com_path}, COM={ser_name}')
    return ser_name, com_path


# 8、保存视频图像的线程
def saved_frames_fun(ser_com, output_thread, output_path, queue_data, frames_rate):
    ser = serial.Serial(ser_com, 115200, timeout=2)
    if ser.isOpen():
        while True:
            data1 = ser.readline().strip()
            # 判断是否为数字
            if data1.isdigit():
                num1 = int(data1)
                print("已接收到整数：", num1)
                if 0 <= num1 <= 2 or 357 < num1 <= 359:
                    video_queue.queue.clear()
                    break
            else:
                print('err: ', data1)

        with tiffile.TiffWriter(output_path, bigtiff=True) as tif:
            while True:
                try:
                    frame_data = queue_data.get(timeout=1)
                    tif.write(frame_data, metadata={'fps': frames_rate})
                except queue.Empty:
                    if not output_thread.is_alive():
                        break
        print(f'saved_thread to stop!')


if __name__ == '__main__':
    # 1、获取连接个数
    acquire_connect_num()
    # 2、开启相机
    camera_id = open_camera()
    # 3、设置参数自动并设置RIO
    image_high, image_width, image_type = set_camera_param(camera_id)
    # 4、开启视频并自动调节固定帧数后关闭
    action_set(camera_id, image_high, image_width, 100)
    video_queue = queue.Queue()
    # 5、开始视频录制线程
    video_thread = threading.Thread(target=action_video_fun, args=(camera_id,
                                                                   image_high, image_width,
                                                                   video_queue,))
    video_thread.start()
    # 6、检测帧率至稳定
    frame_rate = _calculate_fps(video_queue)
    # 7、保存视频前的准备
    port_name, save_path = _pre_paper(frame_rate)

    # 8、满足条件则开启保存视频图像的线程
    if save_path != '' and port_name != '':
        saved_thread = threading.Thread(target=saved_frames_fun, args=(port_name,
                                                                       video_thread,
                                                                       save_path,
                                                                       video_queue,
                                                                       frame_rate))
        saved_thread.start()
        video_thread.join()
        saved_thread.join()
